//rebuild.cpp
//author: aandriyash

#include "utils.h"

#include <stdio.h>
#include <cstring>

#include <iostream>

int RebuildRAID01(
    int totalDisks, 
    int totalBlocks, 
    int blockSize
) 
{
    int failedCounter = 0;
    
    RETURN_IF_ERROR(Mapping(totalDisks, totalBlocks, blockSize, true));

    for (int i = 1; i < totalDisks + 1; i++) 
    {
        if (NULL == ppMappedDataArray[i]) 
        {
            failedCounter++;
        }
    }
    
    if (failedCounter > (totalDisks/2)) 
    {
        std::cout << "Too many disks are failed. Can't rebuild RAID.\n";
        return 1;
    }
    
    int* pFailedDisks = new int[failedCounter];
    int k = 0;
    for(int i = 1; i < totalDisks + 1; i++) 
    {
        if (NULL != ppMappedDataArray[i]) 
        {
            pFailedDisks[k] = i; 
            k++;
        }
    }

    for (int j = 1; j < failedCounter + 1; j++) 
    {
        for(int i = 1; i < failedCounter + 1; i++) 
        {
            if (pFailedDisks[j - 1] + (totalDisks / 2) == pFailedDisks[i - 1])
            {
                std::cout << "Can't rebuild Disk" << pFailedDisks[j - 1] << ". Can't rebuild RAID.\n";
                delete[] pFailedDisks;
                return 1;
            }
        }
    }

    for (int i = 1; i < failedCounter + 1; i++) 
    {
        if ( NULL == ppMappedDataArray[i]) 
        {
            char name[10];
            sprintf(name, "Disk%i", pFailedDisks[i - 1] );
            FILE* pDisk = fopen(name, "wb+");
            
            if (NULL == pDisk) 
            {
                std::cout << "Can't create Disk" << pFailedDisks[i - 1] << ". Can't rebuild RAID.\n";
                delete[] pFailedDisks;
                return 1;
            }
            
            if (pFailedDisks[i - 1] < ((totalDisks / 2) + 1))
            {
                fwrite( ppMappedDataArray[pFailedDisks[i - 1] + (totalDisks / 2)]
                        , sizeof(char)
                        , blockSize * totalBlocks
                        , pDisk
                );
            }
            else
            {
                fwrite( ppMappedDataArray[pFailedDisks[i - 1] - (totalDisks / 2)]
                        , sizeof(char)
                        , blockSize * totalBlocks
                        , pDisk
                );
            }
            
            
            fclose(pDisk);
            delete[] pFailedDisks;

        }
    }
    
    RETURN_IF_ERROR(Unmapping(totalDisks, totalBlocks, blockSize, true));
    
    return 0;
}
int RebuildRAID10(
    int totalDisks, 
    int totalBlocks, 
    int blockSize
) 
{
    RETURN_IF_ERROR(Mapping(totalDisks, totalBlocks, blockSize, true));
    
    int failedCounter = 0;
    
    for (int i = 1; i < totalDisks + 1; i++) 
    {
        if (NULL == ppMappedDataArray[i]) 
        {
            failedCounter++;
        }
    }
    
    if (failedCounter > (totalDisks / 2)) 
    {
        std::cout << "Too many disks are failedCounter. Can't rebuild RAID.\n";
        return 1;
    }

    int* pFailedDisks = new int[failedCounter];
    int k = 0;
    for (int i = 1; i < totalDisks + 1; i++) 
    {
        if (NULL == ppMappedDataArray[i]) 
        {
            pFailedDisks[k] = i;
            k++;
            break;
        }
    }
    for (int j = 1; j < failedCounter + 1; j++) 
    {
        for (int i = 1; i < failedCounter + 1; i++) 
        {
            if ( ((pFailedDisks[j - 1] % 2 != 0) 
                                && (pFailedDisks[j - 1] + 1 == pFailedDisks[i - 1]))
               || ((pFailedDisks[j - 1] % 2 == 0)
                                &&(pFailedDisks[j - 1] - 1 == pFailedDisks[i - 1]))
            )
            {
                std::cout << "Can't rebuild Disk" << pFailedDisks[j - 1] << ". Can't rebuild RAID.\n";
                delete[] pFailedDisks;
                return 1;
            }
        }
    }

    for (int i = 1; i < failedCounter + 1; i++) 
    {
        FILE* pDisk;
        char name[10];
        sprintf(name, "Disk%i", pFailedDisks[i - 1]);
        pDisk = fopen(name, "wb+");
        
        if (pDisk == NULL) 
        {
            std::cout << "Can't create file Disk" << pFailedDisks[i - 1]<< ". Can't rebuild RAID.\n";
            delete[] pFailedDisks;
            return 1;
        }
        if (pFailedDisks[i - 1] % 2 != 0)
        {
            fwrite( ppMappedDataArray[pFailedDisks[i - 1] + 1]
                  , sizeof(char)
                  , blockSize * totalDisks
                  , pDisk
            );
        }
        else
        {
            fwrite( ppMappedDataArray[pFailedDisks[i - 1] - 1]
                  , sizeof(char)
                  , blockSize * totalDisks
                  , pDisk
            );
        }
        fclose(pDisk);
    }
    
    RETURN_IF_ERROR(Unmapping(totalDisks, totalBlocks, blockSize, true));
    delete[] pFailedDisks;
    
    return 0;
}

int main(
    int argc, 
    char* argv[]
) 
{
    RaidConfig raidConf;
    raidConf.Read();     
    
    RETURN_IF_ERROR_TRACE(!IsDiskCrashed(raidConf.totalDisks, true), ("No need to rebuild RAID.\n")); 

    int err = 0;
    switch (raidConf.raidLevel)
    {
        case 1:
            err =  RebuildRAID01(raidConf.totalDisks, raidConf.totalBlocks, raidConf.blockSize);
            break;
        case 10:
            err =  RebuildRAID10(raidConf.totalDisks, raidConf.totalBlocks, raidConf.blockSize);
            break;
        default:
            err = 1;
        break;
    }
    
    return err;
}

